﻿using System;
using System.Linq;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;

namespace Noria.Web.MyMSDN.Checksum
{
    internal class HashCalculator
    {
        private readonly FileInfo _file;
        private readonly Control _browseBtn;
        private readonly BackgroundWorker _worker;
        private readonly Uri _root;

        private string _sha1, _ed2k;

        // 64k has the best performance!
        private const int BlockPiece = 1 * 1024 * 1024;
        private const int BlockSize = 16 * 1024; // 16K

        public HashCalculator(FileInfo file, Control browseBtn)
        {
            _file = file;
            _browseBtn = browseBtn;
            _root = HtmlPage.Document.DocumentUri;

            _worker = new BackgroundWorker();
            _worker.WorkerReportsProgress = true;
            _worker.DoWork += WorkerDoWork;
            _worker.ProgressChanged += WorkerProgressChanged;
            _worker.RunWorkerCompleted += WorkerRunWorkerCompleted;
        }


        //public FileInfo Target { get; set; }
        //public string SHA1 { get; private set; }
        //public string ED2K { get; private set; }

        //public HashOperator(TextBox inputFile, Button btnBrowse, ProgressBar pgbHash)
        //{
        //    this.inputFile = inputFile;
        //    this.btnBrowse = btnBrowse;
        //    this.pgbHash = pgbHash;
        //    this.worker = new BackgroundWorker();

        //    worker.WorkerReportsProgress = true;
        //    worker.DoWork += Worker_DoWork;
        //    worker.ProgressChanged += Worker_ProgressChanged;
        //    worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
        //}


        public void Calc()
        {
            _browseBtn.IsEnabled = false;
            HtmlPage.Window.Eval("window.Navi.instance.checksum.beginCalc('" + _file.Name + "'," + _file.Length + ")");

            //HtmlPage.Window.Invoke("beginCheck");
            //inputFile.Text = String.Format("{0} ({1} bytes)", Target.Name, Target.Length.ToString("0,0"));

            //btnBrowse.IsEnabled = false;
            //pgbHash.Value = 0;

            _worker.RunWorkerAsync();
        }


        private void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //pgbHash.Value = (double)e.UserState * 100;
            var progress = (double)e.UserState;
            HtmlPage.Window.Eval("window.Navi.instance.checksum.updateProgress(" + progress + ")");
        }

        private void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            var sha1 = new AsyncSHA1();
            var ed2k = new AsyncED2K();
            byte[] buffer = new byte[BlockPiece];

            using (Stream s = _file.OpenRead())
            {
                int read = 0;
                int ellapsed = 0;
                do
                {
                    read = s.Read(buffer, 0, BlockPiece);
                    if (read > 0)
                    {
                        sha1.HashCore(buffer, 0, read);
                        ed2k.HashCore(buffer, 0, read);

                        ellapsed += read;

                        // set percentage to userState instead of percentProgress
                        _worker.ReportProgress(0, (ellapsed / (double)_file.Length));
                    }
                }
                while (read > 0);
            }

            _sha1 = BitConverter.ToString(sha1.HashFinal()).Replace("-", "").ToUpper();
            _ed2k = BitConverter.ToString(ed2k.HashFinal()).Replace("-", "").ToUpper();
        }

        private void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            var request = WebRequest.CreateHttp(GetChecksumUri());
            request.BeginGetResponse(CipherResponseReady, request);
        }

        private string GetChecksumUri()
        {
            return GetRootPath() + "/mm/svc/checksum.ashx";
        }

        private string GetRootPath()
        {
            var path = _root.Scheme + "://" + _root.Host + ":" + _root.Port;
            return path;
        }

        private void CipherResponseReady(IAsyncResult ar)
        {
            var request = ar.AsyncState as HttpWebRequest;
            using (var response = request.EndGetResponse(ar))
            {            
                var cipher = ReadFully(response.GetResponseStream());

                var rawText = string.Join("|", _sha1, _ed2k, _file.Length.ToString());
                var encrypted = SimpleRSA.Encrypt(Encoding.UTF8.GetBytes(rawText), cipher);

                var request2 = WebRequest.CreateHttp(GetChecksumUri());
                request2.Method = "POST";
                request2.BeginGetRequestStream(ChecksumRequestReady,
                    new ChecksumRequest(request2, encrypted));
            }

            //Deployment.Current.Dispatcher.BeginInvoke(() =>
            //{
            //    // update ui
            //    var func = string.Format("window.Navi.instance.checksum.endCalc({0},'{1}','{2}',{3})",
            //        0, _file.Name, _sha1, _file.Length);
            //    HtmlPage.Window.Eval(func);
            //    _browseBtn.IsEnabled = true;
            //});
        }

        private void ChecksumRequestReady(IAsyncResult ar)
        {
            var cs = ar.AsyncState as ChecksumRequest;
            using (var stream = cs.Request.EndGetRequestStream(ar))
            {
                stream.Write(cs.Data, 0, cs.Data.Length);
            }
            cs.Request.BeginGetResponse(ChecksumResponseReady, cs.Request);
        }

        private void ChecksumResponseReady(IAsyncResult ar)
        {
            var request = ar.AsyncState as HttpWebRequest;
            using (var response = request.EndGetResponse(ar))
            {
                var raw = ReadFully(response.GetResponseStream());
                var list = Encoding.UTF8.GetString(raw, 0, raw.Length).Split('|');

                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    // update ui
                    // p0: 0 - not found, 1 - existing, 2 - new, 3 - abnormal
                    // p1: file name
                    // p2: sha1
                    // p3: size
                    var func = string.Format("window.Navi.instance.checksum.endCalc({0},'{1}','{2}',{3})",
                        list.Cast<Object>().ToArray());
                    HtmlPage.Window.Eval(func);
                    _browseBtn.IsEnabled = true;
                });
            }


        }

        private static byte[] ReadFully(Stream input)
        {
            var buffer = new byte[BlockSize];
            using (var ms = new MemoryStream())
            {
                int read;
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }


        private class ChecksumRequest
        {
            public ChecksumRequest(HttpWebRequest request, byte[] data)
            {
                Request = request;
                Data = data;
            }

            public HttpWebRequest Request { get; private set; }
            public byte[] Data { get; private set; }
        }
    }
}